﻿using System;
using System.Linq.Expressions;
using System.Reflection;

namespace UnitBox {
	public class Intercept {

		#region public methods

		/// <summary>
		/// Configure the next call to the given static method to be intercepted.
		/// </summary>
		/// <param name="staticCallExpression">the static method call to intercept</param>
		public static InterceptNextStaticCallConfiguration NextCallTo( Expression<Action> staticCallExpression ) {
			// ensure expression is a static call
			var methodCallExpression = staticCallExpression.Body as MethodCallExpression;
			if( methodCallExpression == null || !methodCallExpression.Method.IsStatic )
				throw new ArgumentException( "Expression must be a call to a static method." );

			// do nothing if interception is locked down
			if( UnitBoxInterceptions.IsLockedDown )
				return new InterceptNextStaticCallConfiguration(
					methodCallExpression,
					new ConfigureLaterInterceptionBehaviour( staticCallExpression ));

			// configure the interception queue to intercept the next instance to the given static method
			var interceptionBehaviour = new ConfigureLaterInterceptionBehaviour( staticCallExpression );
			InterceptionQueue.InterceptNextCallTo( methodCallExpression.Method, interceptionBehaviour );

			return new InterceptNextStaticCallConfiguration( methodCallExpression, interceptionBehaviour );
		}


		/// <summary>
		/// Configure the next call to the given static method to be intercepted.
		/// </summary>
		/// <param name="staticCallExpression">the static method call to intercept</param>
		public static InterceptNextStaticCallConfiguration<TReturnValue> NextCallTo<TReturnValue>( Expression<Func<TReturnValue>> staticCallExpression ) {
			if( staticCallExpression == null )
				throw new ArgumentNullException( "staticCallExpression" );

			// if expression is a static method call
			if( staticCallExpression.Body is MethodCallExpression) {
				var methodCallExpression = (MethodCallExpression) staticCallExpression.Body;
				MethodInfo method = methodCallExpression.Method;
				if( !method.IsStatic )
					throw new ArgumentException( "Expression must be a call to a static method." );

				// do nothing if interception is locked down
				if( UnitBoxInterceptions.IsLockedDown )
					return new InterceptNextStaticCallConfiguration<TReturnValue>(
						methodCallExpression,
						new ConfigureLaterInterceptionBehaviour<TReturnValue>( staticCallExpression ) );

				// configure the interception queue to intercept the next instance to the given static method
				// TODO(design): what should the interception behaviour be here? does DoNothing make sense?
				var interceptionBehaviour = new ConfigureLaterInterceptionBehaviour<TReturnValue>( staticCallExpression );
				InterceptionQueue.InterceptNextCallTo( methodCallExpression.Method, interceptionBehaviour );

				return new InterceptNextStaticCallConfiguration<TReturnValue>( methodCallExpression, interceptionBehaviour );

			// if expression is a static property getter
			} else if( staticCallExpression.Body is MemberExpression ) {
				var memberExpression = (MemberExpression) staticCallExpression.Body;
				MemberTypes memberType = memberExpression.Member.MemberType;
				if( memberType != MemberTypes.Property )
					throw new ArgumentException( "Expression must be a call to a static property." );

				var property = (PropertyInfo) memberExpression.Member;
				MethodInfo getterMethod = property.GetGetMethod();
				if( !getterMethod.IsStatic )
					throw new ArgumentException( "Expression must be a call to a static property." );

				// do nothing if interception is locked down
				if( UnitBoxInterceptions.IsLockedDown )
					return new InterceptNextStaticCallConfiguration<TReturnValue>(
						memberExpression,
						new ConfigureLaterInterceptionBehaviour<TReturnValue>( staticCallExpression ));

				// configure the interception queue to intercept the next instance to the given static method
				// TODO(design): what should the interception behaviour be here? does DoNothing make sense?
				var interceptionBehaviour = new ConfigureLaterInterceptionBehaviour<TReturnValue>( staticCallExpression );
				InterceptionQueue.InterceptNextCallTo( getterMethod, interceptionBehaviour );

				return new InterceptNextStaticCallConfiguration<TReturnValue>( memberExpression, interceptionBehaviour );
			}

			throw new ArgumentException( "Expression must be a call to a static method or property." );
		}


		/// <summary>
		/// Returns whether an interception was performed for the given method.
		/// Returns true regardless of whether parameter values match or not.
		/// </summary>
		/// <param name="staticCallExpression"></param>
		/// <returns></returns>
		public static bool WasMethodCalledWithAnyParameters( Expression<Action> staticCallExpression ) {
			// ensure expression is a static call
			var methodCallExpression = staticCallExpression.Body as MethodCallExpression;
			if( methodCallExpression == null || !methodCallExpression.Method.IsStatic )
				throw new ArgumentException( "Expression must be a call to a static method." );

			return InterceptionQueue.WasInterceptedWithAnyParameters( methodCallExpression );
		}


		/// <summary>
		/// Returns whether an interception was performed for the given method.
		/// Returns true only if parameter values match exactly.
		/// </summary>
		/// <param name="staticCallExpression"></param>
		/// <returns></returns>
		public static bool WasMethodCalledWithExactParameters( Expression<Action> staticCallExpression ) {
			// ensure expression is a static call
			var methodCallExpression = staticCallExpression.Body as MethodCallExpression;
			if( methodCallExpression == null || !methodCallExpression.Method.IsStatic )
				throw new ArgumentException( "Expression must be a call to a static method." );

			return InterceptionQueue.WasInterceptedWithExactParameters( methodCallExpression );
		}

		#endregion

	}
}
